--MainUI.lua
require 'fairygui.Window'
local tinsert = table.insert
local tremove = table.remove
local tsort   = table.sort
local smatch  = string.match
local tconcat = table.concat
local sformat = string.format
local tcopy   = table.deepcopy

local REC_MODIFY  = 1
local REC_DELETE  = 2
local REC_INSERT  = 3

MainUI = class(Window)
local prop = property(MainUI)
prop:accessor("store", nil)
prop:accessor("sheet", nil)
prop:accessor("sheetKey", nil)
prop:accessor("sheetVal", nil)
prop:accessor("is_remote", false)
prop:accessor("db_name", "")
prop:accessor("db_path", "")
prop:accessor("tablist", nil)
prop:accessor("tipControl", nil)
prop:accessor("selectSheet", nil)
prop:accessor("selectSheetKey", nil)
prop:accessor("inputSearch", nil)

prop:accessor("tableList", nil)
prop:accessor("sourceTables",{})

prop:accessor("childList", nil)
prop:accessor("sourceChilds",{})

prop:accessor("codeList", nil)
prop:accessor("sourceCodes", {})
prop:accessor("curCodes", {})
prop:accessor("codeNode", nil)
prop:accessor("codeRecords", {})    -- 修改记录
prop:accessor("focusLine", nil)
prop:accessor("codeSelectedLines", {})
prop:accessor("inputCodeSearch", nil)
prop:accessor("textCodeSearchInfo", nil)
prop:accessor("codeSearchRecords", {})
prop:accessor("curCodeSearchIndex", 0)
prop:accessor("curCodeSearchNode", nil)

-- 表的菜单
prop:accessor("tableMenu", nil)
prop:accessor("tableMenuNode", nil)
-- 子项菜单
prop:accessor("childMenu", nil)
prop:accessor("childMenuNode", nil)
-- code的菜单
prop:accessor("codeMenu", nil)
prop:accessor("codeMenuNode", nil)
-- 删除过滤的表
prop:accessor("delFilters", {})

function MainUI:__init()
    self:loadLayout("MainUI", "Main")
    g_event_mgr:addListener(self, "onStoreSwap")
    g_event_mgr:addListener(self, "onStoreLoad")
    g_event_mgr:addListener(self, "onGoHome")
    self.delFilters["QUANTA"] = 1
    self.delFilters["character_world"] = 1
    self.delFilters["game_world"] = 1
end

-- 打开UI执行一次
function MainUI:initComponent()
    local ok, cancel = self:openDbFile()
    if not ok or cancel then
        self:openGUI("StartUI", true)
        if not cancel then
             g_event_mgr:notifyListener("onWarnTip", "存档损坏!")
        end
        return
    end
    g_keycode_mgr:addListener(self, "OnSave", KeyCode.LeftControl, KeyCode.S)
    g_keycode_mgr:addListener(self, "OnOpen", KeyCode.LeftControl, KeyCode.O)
    g_keycode_mgr:addListener(self, "OnReset", KeyCode.LeftControl, KeyCode.R)
    g_keycode_mgr:addListener(self, "OnCodeSearch", KeyCode.LeftControl, KeyCode.F)
end

-- 窗口关闭
function MainUI:onClose()
    self.selectSheet = nil
    self.selectSheetKey = nil
    g_keycode_mgr:RemoveListener(self, "OnSave")
    g_keycode_mgr:RemoveListener(self, "OnOpen")
    g_keycode_mgr:RemoveListener(self, "OnReset")
end

-- 初始化数据
function MainUI:initData(data)
    print("[MainUI][initData] data:{}", json.encode(data))
    self.is_remote = data.is_remote
    self.db_name = data.db_name
end

function MainUI:onStoreLoad(store)
    self.sheet = nil
    self.sheetKey = nil
    self:updateTables(store)
    self:updateSheets(store)
end

function MainUI:onStoreSwap(store)
    self.sheet = nil
    self.sheetKey = nil
    self:updateSheets(store)
end

function MainUI:onGoHome()
    self:close()
    self:openGUI("StartUI", true)
end

function MainUI:OnSave()
    self:saveStore(function()
        
        g_event_mgr:notifyListener("onSuccessTip", "保存成功!")
    end)
end

function MainUI:OnReset()
    self:refreshCodeList(self.sourceCodes)
    self.codeRecords = {}
    self:upTipControl()
end

function MainUI:OnCodeSearch()
    if self:getController("codeSearch").selectedIndex == 0 then
        self:getController("codeSearch").selectedIndex = 1
        self:InputRequestFocus(self.inputCodeSearch)
    else
        self:CloseCodeSearch()
    end
end

function MainUI:CloseCodeSearch()
    self.codeSearchRecords = {}
    self.inputCodeSearch.text = ""
    if self.curCodeSearchNode then
        self.curCodeSearchNode.selected = false
    end
    self:getController("codeSearchInfo").selectedIndex = 0
    self:getController("codeSearch").selectedIndex = 0
end

function MainUI:OnOpen()
    local ok, cancel = self:openDbFile()
    if not ok or cancel then
        if not cancel then
             g_event_mgr:notifyListener("onWarnTip", "存档损坏!")
        end
        return
    end
end

-- 刷新代码列表
function MainUI:refreshCodeList(codes)
    self.curCodes = {}
    self.codeList.numItems = 0
    self.curCodes = tcopy(codes)
    self.codeList.numItems = #self.curCodes
    self.codeList:ClearSelection()
end


function MainUI:initEvent()
    self.tablist = self:getChild("tabs")
    self.tipControl = self:getController("tip")
    self.inputSearch = self:getChild("inputSearch")
    self.inputCodeSearch = self:getChild("inputCodeSearch")
    self.inputCodeSearch.asTextInput.singleLine = true
    self.textCodeSearchInfo = self:getChild("textCodeSearchInfo")
    self.tableList = self:getChild("tableList")
    self.tableList:SetVirtual();
    self.childList = self:getChild("childList")
    self.childList:SetVirtual();
    self.codeList = self:getChild("codeList")
    self.codeList:SetVirtual();

    local tableNodeClick = function(context)
        local info = self.sourceTables[context.sender.data.index]
        self.sheetKey = nil
        self.tableList:SelectNone()
        context.sender.selected = true
        self.selectSheet = info.sheet
        self:updateKeys(info.store, info.sheet, info.values)
    end

    self.tableList.itemRenderer = function(index, node)
        local info = self.sourceTables[index+1]
        node.text = info.sheet
        node.icon = info.icon
        node.data = {
            index = index+1
        }
        self:setWidgetControllerStatus(node, "leaf", 1)
        self:addWidgetClick(node, tableNodeClick)
        node.onRightClick:Set(function()
            self.tableMenuNode = {
                node = node,
                store = info.store,
                sheet = info.sheet,
                values = info.values,
            }
            self.tableMenu:Show();
        end)
        -- 默认选中第一个
        if index == 0 then
            self.tableList:SelectNone()
            node.selected = true
            self:updateKeys(info.store, info.sheet, info.values)
        end
    end

    local childNodeClick = function(context)
        local info = self.sourceChilds[context.sender.data.index]
        self.childList:SelectNone()
        context.sender.selected = true
        self.selectSheetKey = info.key
        self:updateValue(info.store, info.sheet, info.key, info.value)
    end
    self.childList.itemRenderer = function(index, node)
        local info = self.sourceChilds[index+1]
        node.text = info.text
        node.icon = info.icon
        node.data = {
            index = index+1
        }
        self:setWidgetControllerStatus(node, "leaf", 1)
        self:addWidgetClick(node, childNodeClick)
        node.onRightClick:Set(function()
            self.childMenuNode = {
                node = node,
                store = info.store,
                sheet = info.sheet,
                key = info.key,
                text = node.text
            }
            self.childMenu:Show();
        end)
        if index == 0 then
            self.childList:SelectNone()
            node.selected = true
            self:updateValue(info.store, info.sheet, info.key, info.value)
        end
    end

    local codeInputChanged = function(context)
        local line = self.codeNode.data.line
        self.codeRecords[line] = REC_MODIFY
        self.curCodes[line] = context.sender.text
        self.codeNode.text = context.sender.text
        self.codeNode.data.content = context.sender.text
        self:upTipControl()
    end

    local codeNodeClick = function(context)
        if context.sender == self.codeNode then
            return
        end
        if self.codeNode then
            self.codeNode:GetController("edit").selectedIndex = 0
        end
        -- if context.inputEvent.isDoubleClick then
        -- end
        if self.curCodeSearchNode and context.sender ~= self.curCodeSearchNode then
            self.curCodeSearchNode.selected = false
        end
        context.sender:GetController("edit").selectedIndex = 1
        local input = context.sender:GetChild("input")
        input.text = context.sender.text
        self:InputRequestFocus(input)
        input.onChanged:Remove(codeInputChanged)
        input.onChanged:Add(codeInputChanged)
        self.codeNode = context.sender

        self.codeSelectedLines = {}
        -- 获取当前选中的项的索引
        local selectedIndices = self.codeList:GetSelection()
        for _,index in pairs(selectedIndices) do
            local node = self.codeList:GetChildAt(index)
            tinsert(self.codeSelectedLines, node.data.line)
        end
        if #self.codeSearchRecords > 0 then
            local searchLine = self.codeSearchRecords[self.curCodeSearchIndex]
            if context.sender.data.line <= searchLine then
                self.curCodeSearchIndex = 0
            end
        end
    end
    -- 代码列表
    self.codeList.itemRenderer = function(index, obj)
        local line = index+1
        local content = self.curCodes[line]
        obj.text = content
        obj:GetChild("input").text = content
        obj:GetChild("line").text = line
        obj.data = {
            line = line,
            content = content
        }
        obj.onClick:Remove(codeNodeClick)
        obj.onClick:Add(codeNodeClick)
        obj.onRightClick:Set(function()
            self.codeNode = obj
            self.codeMenu:Show()
        end)
        if self.focusLine ~= nil and line == self.focusLine then
            obj:GetController("edit").selectedIndex = 1
            local input = obj:GetChild("input")
            input.text = obj.text
            self:InputRequestFocus(input)
            self.focusLine = nil
        end
    end

    -- 表菜单
    self.tableMenu = PopupMenu();
    self.tableMenu.contentPane.width = 100;
    self.tableMenu:AddItem("删除", function()
        if self.delFilters[self.tableMenuNode.sheet] then
            g_event_mgr:notifyListener("onWarnTip", "核心数据表，禁止执行删除操作。")
            return
        end
        local panel = self:openGUI("Message")
        panel:Show("警告", sformat("此操作不可撤销！确定要删除 [ %s ] 吗?", self.tableMenuNode.sheet), function(result)
            if(not result) then
                g_event_mgr:notifyListener("onWarnTip", "操作取消")
                return
            end
            self.tableMenuNode.store:clear_sheet(self.tableMenuNode.sheet, self.tableMenuNode.values)
            self.tableList:RemoveChild(self.tableMenuNode.node)
            self.childList.numItems = 0
            self:onStoreLoad(self.tableMenuNode.store)
            g_event_mgr:notifyListener("onSuccessTip", "删除成功")
        end)
    end);
    self.tableMenu:AddItem("复制", function()
        SystemUtils.CopyToCliBoard(self.tableMenuNode.sheet)
        g_event_mgr:notifyListener("onSuccessTip", "操作成功!")
    end)

    -- 子项菜单
    self.childMenu = PopupMenu();
    self.childMenu.contentPane.width = 100;
    self.childMenu:AddItem("删除", function()
        if self.delFilters[self.childMenuNode.sheet] then
            g_event_mgr:notifyListener("onWarnTip", "核心数据表，禁止执行删除操作。")
            return
        end
        local panel = self:openGUI("Message")
        panel:Show("警告", sformat("此操作不可撤销！确定要删除 [ %s ] 吗?", self.childMenuNode.text), function(result)
            if(not result) then
                g_event_mgr:notifyListener("onWarnTip", "操作取消")
                return
            end
            self.childMenuNode.store:del(self.childMenuNode.key, self.childMenuNode.sheet)
            self.childMenuNode.store:flush()
            self.childList.numItems = 0
            self:onStoreLoad(self.childMenuNode.store)
            g_event_mgr:notifyListener("onSuccessTip", "删除成功")
        end)
    end);
    self.childMenu:AddItem("复制", function()
        SystemUtils.CopyToCliBoard(self.childMenuNode.key)
        g_event_mgr:notifyListener("onSuccessTip", "操作成功!")
    end)

    -- 代码菜单 
    self.codeMenu = PopupMenu();
    self.codeMenu.contentPane.width = 100;
    self.codeMenu:AddItem("向前插入值", function()
        local line = self.codeNode.data.line
        tinsert(self.curCodes, line, "")
        self.focusLine = line
        self:refreshCodeList(self.curCodes)
        self.codeRecords[line] = REC_INSERT
        self:upTipControl()
    end);
    self.codeMenu:AddItem("向后插入值", function()
        local line = self.codeNode.data.line+1
        tinsert(self.curCodes, line, "")
        self.focusLine = line
        self:refreshCodeList(self.curCodes)
        self.codeRecords[line] = REC_INSERT
        self:upTipControl()
    end);
    self.codeMenu:AddItem("删除值", function()
        for _,line in ipairs(self.codeSelectedLines) do
            tremove(self.curCodes, line)
            self.codeRecords[line] = REC_DELETE
        end
        self:upTipControl()
        self.codeSelectedLines = {}
        self:refreshCodeList(self.curCodes)
    end);
    self.codeMenu:AddItem("清空", function()
        self.focusLine = 0
        for _,line in ipairs(self.codeSelectedLines) do
            self.curCodes[line] = ""
            self.codeRecords[line] = REC_MODIFY
            if self.focusLine == 0 then
                self.focusLine = line
            end
        end
        self:upTipControl()
        self.codeSelectedLines = {}
        self:refreshCodeList(self.curCodes)
    end);
    self.codeMenu:AddItem("复制", function()
        local result = {}
        for _,line in ipairs(self.codeSelectedLines) do
            tinsert(result, self.curCodes[line])
        end
        self.codeSelectedLines = {}
        SystemUtils.CopyToCliBoard(tconcat(result, "\r\n"))
        g_event_mgr:notifyListener("onSuccessTip", "操作成功!")
    end);
    self.codeMenu:AddItem("粘贴", function()
        local line = self.codeNode.data.line
        self.curCodes[line] = SystemUtils.CliBoardToPaste()
        self:refreshCodeList(self.curCodes)
        self.codeRecords[line] = REC_MODIFY
        self:upTipControl()
        g_event_mgr:notifyListener("onSuccessTip", "操作成功!")
    end);
    -- 返回
    self:addClick("Back", function()
        g_store_mgr:close(self.db_name..".db")
        if _G.BackUI then
            self:openGUI(_G.BackUI, true)
            _G.BackUI = nil
            return
        end
        self:openGUI("StartUI", true)
    end)
    -- 打开
    self:addClick("Open", function()
        local ok, cancel = self:openDbFile()
        if not ok or cancel then
            self:openGUI("StartUI", true)
            if not cancel then
                g_event_mgr:notifyListener("onWarnTip", "存档损坏!")
            end
            return
        end
    end)
    -- 保存
    self:addClick("Save", function()
        self:saveStore(function()
            g_event_mgr:notifyListener("onSuccessTip", "保存成功!")
        end)
    end)
    -- 恢复
    self:addClick("Reset", function()
        self:refreshCodeList(self.sourceCodes)
        self.codeRecords = {}
        self:upTipControl()
    end)
    -- 复制
    self:addClick("Copy", function()
        local temps = tcopy(self.curCodes)
        for i = #temps, 1, -1 do
            if temps[i] == "" then
                tremove(temps, i)
            end
        end
        local text = tconcat(temps, "\r\n")
        SystemUtils.CopyToCliBoard(text)
        g_event_mgr:notifyListener("onSuccessTip", "操作成功!")
    end)
    -- 清空
    self:addClick("Clear", function()
        self:clearStore()
    end)
    -- 关闭搜索
    self:addClick("btnCloseSearch", function()
        self:CloseCodeSearch()
    end)
    -- 向上搜索
    self:addClick("btnUpSearch", function()
        self:codeSearchSwitch(false)

    end)
    -- 向下搜索
    self:addClick("btnDownSearch", function()
        self:codeSearchSwitch(true)
    end)
    -- 刷新
    self:addClick("Refresh", function()
        g_store_mgr:close(self.db_name..".db")
        -- 本地刷新
        if not self.is_remote then
            if self:loadDbFile(self.db_path) then
                self:CloseCodeSearch()
                if self.codeNode then
                    self.codeNode.selected = false
                end
                g_event_mgr:notifyListener("onSuccessTip", "刷新成功!")
            else
                g_event_mgr:notifyListener("onWarnTip", "刷新失败!")
            end
        -- 远程刷新
        else
            g_world_mgr:downRemoteDbFile(self.db_name, function(err, data)
                if err then
                    g_event_mgr:notifyListener("onWarnTip", err)
                    print("[MainUI][initEvent] downRemoteDbFile fail dbname:"..self.db_name)
                    return
                end
                if self:loadDbFile(self.db_path) then
                   self:CloseCodeSearch()
                   if self.codeNode then
                        self.codeNode.selected = false
                    end
                   g_event_mgr:notifyListener("onSuccessTip", "刷新成功!")
                else
                    g_event_mgr:notifyListener("onWarnTip", "刷新失败!")
                end
            end)
        end
    end)
    -- 搜索
    self.inputSearch.onChanged:Add(function()
        self:search()
    end)
    -- 代码搜索
    self.inputCodeSearch.onChanged:Add(function()
        self.codeSearchRecords = {}
        self.curCodeSearchIndex = 0
        if self.inputCodeSearch.text == "" then
            self:getController("codeSearchInfo").selectedIndex = 0
            return
        end
        for line,content in ipairs(self.curCodes) do
            local result = string.find(content, self.inputCodeSearch.text)
            if result then
                tinsert(self.codeSearchRecords, line-1)
            end
        end
        if #self.codeSearchRecords > 0 then
            self.textCodeSearchInfo:SetVar("index", 0)
            self.textCodeSearchInfo:SetVar("sum", #self.codeSearchRecords)
            self.textCodeSearchInfo:FlushVars();
            self:getController("codeSearchInfo").selectedIndex = 1
            self:codeSearchSwitch(true)
        else
            self:getController("codeSearchInfo").selectedIndex = 0
        end
    end)
    -- -- 代码搜索
    -- self.inputCodeSearch.onSubmit:Add(function()
    --     self:codeSearchSwitch(true)
    -- end)
    -- 信息
    self:addClick("Info", function()
        self:openGUI("InfoUI")
    end)
    -- Bootom
    self:addClick("btnBootom", function()
        if self.codeList.numItems > 0 then
            self.codeList:ScrollToView(self.codeList.numItems-1)
        end
        if #self.codeSearchRecords > 0 then
            self.curCodeSearchIndex = #self.codeSearchRecords+1
            self.textCodeSearchInfo:SetVar("index", self.curCodeSearchIndex)
            self.textCodeSearchInfo:FlushVars();
            self:codeSearchSwitch(false)
        end
    end)
    -- Top
    self:addClick("btnTop", function()
        self.codeList:ScrollToView(0)
        if #self.codeSearchRecords > 0 then
            self.curCodeSearchIndex = 0
            self.textCodeSearchInfo:SetVar("index", self.curCodeSearchIndex)
            self.textCodeSearchInfo:FlushVars();
            self:codeSearchSwitch(true)
        end
    end)
end

-- 代码切换搜索
function MainUI:codeSearchSwitch(down)
    if #self.codeSearchRecords == 0 then
        return
    end
    if down then
        self.curCodeSearchIndex = self.curCodeSearchIndex + 1
        if self.curCodeSearchIndex > #self.codeSearchRecords then
            self.curCodeSearchIndex = 1
        end
    else
        self.curCodeSearchIndex = self.curCodeSearchIndex - 1
        if self.curCodeSearchIndex < 0 then
            self.curCodeSearchIndex = #self.codeSearchRecords-1
        end
    end
    local line = self.codeSearchRecords[self.curCodeSearchIndex]
    if self.curCodeSearchNode then
        self.curCodeSearchNode.selected = false
    end
    if self.codeNode then
        self.codeNode.selected = false
    end
    self.codeList:ScrollToView(line)
    local topNode = self.codeList:GetChildAt(0)
    local topNodeLine = topNode.data.line
    local curNodeLine = line - topNodeLine + 1
    print("codeSearchSwitch line:"..line.." topNodeLine:"..topNodeLine.." curNodeLine:"..curNodeLine)
    self.curCodeSearchNode = self.codeList:GetChildAt(curNodeLine)
    self.curCodeSearchNode.selected = true
    self.textCodeSearchInfo:SetVar("index", self.curCodeSearchIndex)
    self.textCodeSearchInfo:FlushVars();
end

-- 更新提示控制
function MainUI:upTipControl()
    if next(self.codeRecords) then
        self.tipControl:SetSelectedPage("activate")
    else
        self.tipControl:SetSelectedPage("default")
    end
end

-- 加载db文件
function MainUI:loadDbFile(path)
    if g_store_mgr:load(path) then
        self.db_path = path
        return true
    end
    print("[MainUI][loadDbFile] fail path:"..path)
    return false
end

-- 打开db文件
function MainUI:openDbFile()
    -- 跳转
    if _G.JumpFile then
        if self:loadDbFile(_G.JumpFile) then
            _G.JumpFile = nil
            return true
        end
    end
    -- 直接打开文件
    if _G.OpenFile then
        if self:loadDbFile(_G.OpenFile) then
            _G.OpenFile = nil
            return true
        end
    end
    -- 选择文件打开
    local path = g_world_mgr.game_db_path:gsub("/", "\\")
    local selectedPath = CS.NativeFolderDialog.ChooseWinFile("选择db文件", path, ".db")
    if not selectedPath or selectedPath == "" then
        return false, true
    end
    if self:loadDbFile(selectedPath) then
        return true
    end
    return false
end

function MainUI:updateTables(cstore)
    --清空db列表
    self.tablist:RemoveChildrenToPool()
    local stores = g_store_mgr:getStores()
    for name, store in pairs(stores) do
        local button = self.tablist:AddItemFromPool("ui://nk9ejx23p0yg7r")
        button.text = stdfs.to_utf8(name)
        local callback_click = function()
            if g_store_mgr:get(name) then
                self.tablist:SelectNone()
                button.selected = true
                g_store_mgr:swap(name)
            end
        end
        self:addWidgetClick(button, callback_click)
        if store == cstore then
            self.tablist:SelectNone()
            button.selected = true
        end
        local close_click = function()
            g_store_mgr:close(name)
        end
        self:addWidgetClick(button, close_click, "closeButton")
    end
end

function MainUI:updateSheets(store)
    --清空db列表
    self.tableList.numItems = 0
    if not store then
        self:updateKeys(store)
        return
    end
    local sheets = store:getTables()
    self.sourceTables = {}
    for _, info in pairs(sheets) do
        local sheet, values = table.unpack(info)
        tinsert(self.sourceTables, {
            text = sheet,
            sheet = sheet,
            icon = "ui://0o1dt81p11ctya8",
            values = values,
            store = store
        })
    end
    self.tableList.numItems = #self.sourceTables
end

function MainUI:updateKeys(store, sheet, values)
    self.sheet = sheet
    self.childList.numItems = 0
    if not store then
        self:updateValue(store)
        return
    end
    local keys = {}
    for key, value in pairs(values) do
        local id = tonumber(key)
        tinsert(keys, {
            key = id and id or key,
            value = value
        })
    end
    tsort(keys, function(a, b)
        return a.key < b.key
    end)
    self.sourceChilds = {}
    for _, item in ipairs(keys) do
        local key = item.key
        local addInfo = {
            text = item.key or "",
            key = item.key,
            value = item.value,
            icon = "ui://0o1dt81p11ctyae",
            store = store,
            sheet = sheet
        }
        if self.sheet:sub(1,3) == "npc" then
            local npc_id = tonumber(key)
            local world_id = tostring(store:getId())
            local npcinfo = store:get("world_npc", world_id)
            addInfo.text = npcinfo.npc_ids[npc_id]
        end
        tinsert(self.sourceChilds, addInfo)
    end
    self.childList.numItems = #self.sourceChilds
end

function MainUI:updateValue(store, sheet, key, value)
    self.sheetKey = key
    self.store = store
    if not store then
        return
    end
    local ok, err = pcall(function()
        if type(value) == "table" then
            local codes = store:decode(sheet, key, value)
            self.sourceCodes = tcopy(codes)
            self:refreshCodeList(codes)
            return
        else
            self.sourceCodes = {value}
            self:refreshCodeList({value})
        end
    end)
    if not ok then
        self:getController("showFail").selectedIndex = 1
        print("[Store][updateValue] json decode failed:", err)
    else
        self:getController("showFail").selectedIndex = 0
    end
    if self.tipControl then
        self.tipControl:SetSelectedPage("default")
    end
end

function MainUI:saveStore(cb)
    if self.sheet and self.sheetKey then
        local store = g_store_mgr:getCurStore()
        if store then
            -- 清理为空的数据
            for i = #self.curCodes, 1, -1 do
                if self.curCodes[i] == "" then
                    tremove(self.curCodes, i)
                end
            end
            local sheet_value = tconcat(self.curCodes)
            local value = store:get(self.sheet, self.sheetKey)
            if type(value) == "table" then
                local ok, store_value = pcall(json.decode, sheet_value, true)
                if not ok then
                    g_event_mgr:notifyListener("onWarnTip", "数据格式不正确,请检查!")
                    return
                end
                sheet_value = store_value
            end
            if not self.is_remote then
                store:put(self.sheetKey, sheet_value, self.sheet)
                self:refreshCodeList(self.curCodes)
                self.sourceCodes = tcopy(self.curCodes)
                self.codeRecords = {}
                self:upTipControl()
                cb()
            else
                -- 保存远程数据
                g_world_mgr:saveRemoteDb(self.db_name, self.sheet, self.sheetKey, sheet_value, function(err)
                    if err then
                        g_event_mgr:notifyListener("onWarnTip", err)
                        print("[MainUI][saveStore] saveRemoteDb fail err:"..err)
                        return
                    end
                    self:refreshCodeList(self.curCodes)
                    self.sourceCodes = tcopy(self.curCodes)
                    self.codeRecords = {}
                    self:upTipControl()
                    cb()
                end)
            end
        end
    end
end

function MainUI:clearStore()
    local panel = self:openGUI("Message")
    panel:Show("警告", "此操作不可撤销！您确定要继续清空吗？", function(result)
        if(not result) then
            g_event_mgr:notifyListener("onWarnTip", "操作取消")
            return
        end
        self.curCodes = {"{}"}
        self:saveStore(function()
            self:upTipControl()
            g_event_mgr:notifyListener("onSuccessTip", "操作成功!")
            g_store_mgr:close(self.db_name..".db")
            self:loadDbFile(self.db_path)
        end)
    end)
end

function MainUI:search()
    local searchText = self.inputSearch.text:gsub(" ", "")
    if searchText == "" then
        self:updateSheets(self.store)
        self.tableList.numItems = #self.sourceTables
        self.tableList:RefreshVirtualList()
        return
    end

    -- table搜索
    searchText = searchText:lower()
    for i = #self.sourceTables, 1, -1 do
        local text = self.sourceTables[i].text:lower()
        if not smatch(text, searchText) then
            tremove(self.sourceTables, i)
        end
    end
    self.tableList.numItems = #self.sourceTables
    self.tableList:RefreshVirtualList()
end
